/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */

package org.griphyn.vdl.classes;

import org.griphyn.vdl.classes.*;
import java.util.*;
import java.io.IOException;
import java.io.Writer;
import java.io.Serializable;

/**
 * <code>Use</code> is employed to reference bound actual arguments. Note
 * that actual arguments are either of type <code>Scalar</code> or 
 * of type <code>List</code>. Each argument has a preferred linkage that
 * is optionally repeated in this usage class.
 *
 * <code>Use</code> extends the base class <code>Leaf</code> by adding
 * most attributes of all siblings.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 50 $
 *
 * @see Leaf
 * @see Text
 * @see LFN
 *
 * @see Value
 * @see Scalar
 * @see List
 */
public class Use extends Leaf implements Cloneable, Serializable 
{
  /**
   * The linkage type when refering to an argument that contains a filename.
   * Legal values range from <code>LFN#NONE</code> to <code>LFN#INOUT</code>.
   * The initial value is used to flag the non-initialized state.
   *
   * @see LFN#NONE
   * @see LFN#INPUT
   * @see LFN#OUTPUT
   * @see LFN#INOUT
   * @see LFN#isInRange(int)
   */
  private int m_link = -1;

  /**
   * Stores the name of the bound variable from the actual argument. 
   * This must not be empty. A value must be filled in to reach a valid
   * object state.
   */
  private String m_name;

  /**
   * Stores the prefix string to be used when rendering a <code>List</code>.
   * Unused for <code>Scalar</code> content.
   *
   * @see Value
   * @see Scalar
   * @see List
   */
  private String m_prefix;

  /**
   * Stores the separator string used when rendering a <code>List</code>.
   * Unused for <code>Scalar</code> content.
   *
   * @see Value
   * @see Scalar
   * @see List
   */
  private String m_separator;

  /**
   * Stores the suffix string to terminate a <code>List</code> rendering with.
   * Unused for <code>Scalar</code> content.
   *
   * @see Value
   * @see Scalar
   * @see List
   */
  private String m_suffix;

  /**
   * Creates and returns a copy of this object.
   * @return a new instance.
   */
  public Object clone()
  {
    Use result = new Use( this.m_name, this.m_prefix, this.m_separator, this.m_suffix );
    result.setAnyLink( this.m_link );
    return result;
  }

  /**
   * Default ctor. Creates an empty object that is not valid due
   * to the lack of a bound variable name. To be used by the SAX
   * parser.
   */
  public Use()
  { 
    super(); 
    this.m_separator = " "; // default per XML Schema
    this.m_prefix = this.m_suffix = "";
  }

  /**
   * Convenience ctor. Creates an empty object with a bound argument
   * name. This ctor should be used by outside applications to assure
   * proper initialization of the bound argument name.
   *
   * @param name is the name of the bound argument to remember.
   */
  public Use( String name )
  {
    super();
    this.m_name = name;
    this.m_separator = " "; // default per XML Schema
    this.m_prefix = this.m_suffix = "";
  }

  /**
   * Convenience ctor. Creates an object with a bound argument name.
   * This ctor should be used by outside applications to assure proper
   * initialization of the bound argument name. 
   *
   * @param name is the name of the bound argument to remember.
   * @param prefix is a prefix when rendering list content into a string.
   * @param separator is a string to be placed between list elements when
   *                  rendering a list.
   * @param suffix is a suffix when rendering list content into a string.
   *
   * @see Scalar
   * @see List
   */
  public Use( String name, String prefix, String separator, String suffix )
  {
    super();
    this.m_name = name;
    this.m_prefix = prefix;
    this.m_separator = separator;
    this.m_suffix = suffix;
  }

  /**
   * Convenience ctor. Creates an object with a bound argument name.
   * This ctor should be used by outside applications to assure proper
   * initialization of the bound argument name. 
   *
   * @param name is the name of the bound argument to remember.
   * @param link is the linkage type of the bound argument for type checking.
   * @throws IllegalArgumentException if the linkage is not
   *         within the legal range between {@link LFN#NONE} and
   *         {@link LFN#INOUT}.
   */
  public Use( String name, int link )
    throws IllegalArgumentException
  {
    super();
    this.m_name = name;
    this.m_separator = " "; // default per XML Schema
    this.m_prefix = this.m_suffix = "";
    if ( LFN.isInRange(link) )
      this.m_link = link;
    else
      throw new IllegalArgumentException();
  }

  /**
   * Accessor: Obtains the current state of the linkage.
   *
   * @return the linkage value. The returned value might be -1 to indicate
   * that the linkage was not initialized. Note that -1 is an out of range
   * value for linkage.
   * @see #setLink(int)
   */
  public int getLink()
  { 
    return this.m_link; 
  }

  /**
   * Accessor: Obtains the name of the bound actual argument.
   *
   * @return the bound name. A misconfigured object might return an empty
   * or null string.
   * @see #setName(String)
   */
  public String getName()
  { 
    return this.m_name; 
  }

  /**
   * Accessor: Obtains the current prefix rendering information. The 
   * prefix is used in {@link List} rendering as front bracket. 
   *
   * @return the prefix rendering string, which might be null or empty.
   * @see #setPrefix(String)
   */
  public String getPrefix()
  { 
    return this.m_prefix; 
  }

  /**
   * Accessor: Obtains the current separator rendering information. The 
   * separator is used in {@link List} rendering as element separator.
   *
   * @return the separator rendering string, which might be null or empty.
   * @see #setSeparator(String)
   */
  public String getSeparator()
  { 
    return this.m_separator; 
  }

  /**
   * Accessor: Obtains the current suffix rendering information. The 
   * suffix is used in {@link List} rendering as rear bracket. 
   *
   * @return the suffix rendering string, which might be null or empty.
   * @see #setSuffix(String)
   */
  public String getSuffix()
  { 
    return this.m_suffix; 
  }

  /**
   * Accessor: Sets the linkage of the bound argument.
   * @param link is the linkage value as integer within the range.
   * @throws IllegalArgumentException if the linkage is not
   *         within the legal range between {@link LFN#NONE} and
   *         {@link LFN#INOUT}.
   * @see #getLink()
   * @see LFN#NONE
   * @see LFN#INPUT
   * @see LFN#OUTPUT
   * @see LFN#INOUT
   * @see LFN#isInRange(int)
   */
  public void setLink( int link )
    throws IllegalArgumentException
  { 
    if ( LFN.isInRange(link) )
      this.m_link = link; 
    else
      throw new IllegalArgumentException();
  }

  private void setAnyLink( int link )
  { this.m_link = link; }

  /**
   * Accessor: Sets or overwrites the name of the bound argument.
   * Do not use empty or null strings here.
   *
   * @param name is the new variable name to remember.
   * @see #getName()
   */
  public void setName( String name )
  { this.m_name = name; }

  /**
   * Accessor: Sets or overwrites the current prefix rendering information.
   * The prefix is used in {@link List} rendering as front bracket. 
   *
   * @param prefix is a rendering string, which might be null or empty.
   * @see #getPrefix()
   */
  public void setPrefix( String prefix )
  { 
    this.m_prefix = prefix; 
  }

  /**
   * Accessor: Sets or overwrites the current separator rendering information.
   * The separator is used between {@link List} element during rendering. 
   *
   * @param separator is a rendering string, which might be null or empty.
   * @see #getSeparator()
   */
  public void setSeparator( String separator )
  { 
    this.m_separator = separator; 
  }

  /**
   * Accessor: Sets or overwrites the current suffix rendering information.
   * The suffix is used in {@link List} rendering as rear bracket. 
   *
   * @param suffix is a rendering string, which might be null or empty.
   * @see #getSuffix()
   */
  public void setSuffix( String suffix )
  { 
    this.m_suffix = suffix; 
  }

  /**
   * Dump content of this instance representation into a stream.<p>
   * FIXME: The rendering information is not dumped into the non-XML output.
   *
   * @param stream is a stream opened and ready for writing. This can
   * also be a string stream for efficient output. The stream interface
   * should be able to handle large elements efficiently.
   * @exception IOException if something fishy happens to the stream.
   */
  public void toString( Writer stream )
    throws IOException
  {
    boolean has_fix = ( this.m_prefix != null && this.m_prefix.length() > 0 ||
			this.m_suffix != null && this.m_suffix.length() > 0 );
    boolean has_sep = ( this.m_separator == null || ! this.m_separator.equals(" ") );

    if ( has_fix || has_sep ) {
      // must use the tedious version
      stream.write( "${" );
      if ( has_fix ) {
	// this is the ${pre:sep:suf|link:id} version
	stream.write('"');
	if ( this.m_prefix != null ) stream.write( escape(this.m_prefix) );
	stream.write("\":\"");
	if ( this.m_separator != null ) stream.write( escape(this.m_separator) );
	stream.write("\":\"");
	if ( this.m_suffix != null ) stream.write( escape(this.m_suffix) );
	stream.write("\"|");
      } else if ( has_sep ) {
	// this is the ${sep|link:id} version, mind that " " is IMPLIED!
	// thus, ${""|link:id} is the output for any null separator, while
	// ${link:id} will be the output for a space separator.
	stream.write('"');
	if ( this.m_separator != null ) stream.write( escape(this.m_separator) );
	stream.write("\"|");
      }

      if ( LFN.isInRange(this.m_link) ) {
	stream.write( LFN.toString(this.m_link) ); // no need to escape()
	stream.write( ':' );
      }
      stream.write( escape(this.m_name) );
      stream.write( '}' );
    } else if ( LFN.isInRange(this.m_link) ) {
      // use the type-casting version
      stream.write( '(' );
      stream.write( LFN.toString(this.m_link) ); // no need to escape()
      stream.write( ')' );
      stream.write( escape(this.m_name) );
    } else {
      // can use minimal version
      stream.write( escape(this.m_name) );
    }
  }

  /**
   * Dump the state of the current element as XML output. This function
   * traverses all sibling classes as necessary, and converts the data
   * into pretty-printed XML output. The stream interface should be able
   * to handle large output efficiently, if you use a buffered writer.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @param indent is a <code>String</code> of spaces used for pretty
   * printing. The initial amount of spaces should be an empty string.
   * The parameter is used internally for the recursive traversal.
   * If a <code>null</code> value is specified, no indentation nor
   * linefeeds will be generated. 
   * @param namespace is the XML schema namespace prefix. If neither
   * empty nor null, each element will be prefixed with this prefix,
   * and the root element will map the XML namespace. 
   * @exception IOException if something fishy happens to the stream.
   */
  public void toXML( Writer stream, String indent, String namespace )
    throws IOException
  {
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( '<' );
    if ( namespace != null && namespace.length() > 0 ) {
      stream.write( namespace );
      stream.write( ':' );
    }
    stream.write( "use" );
    writeAttribute( stream, " name=\"", this.m_name );
    if ( LFN.isInRange(this.m_link) ) 
      writeAttribute( stream, " link=\"", LFN.toString(this.m_link) );
    if ( this.m_prefix != null && this.m_prefix.length() > 0 )
      writeAttribute( stream, " prefix=\"", this.m_prefix );

    // If the separator is empty, write it. We may not need to write it, 
    // if the separator is a space.
    if ( this.m_separator == null || ! this.m_separator.equals(" ") ) {
      stream.write( " separator=\"" );
      if ( this.m_separator != null ) 
	stream.write( quote(this.m_separator,true) );
      stream.write( '\"' );
    }

    if ( this.m_suffix != null && this.m_suffix.length() > 0 )
      writeAttribute( stream, " suffix=\"", this.m_suffix ) ;

    stream.write( "/>" );
    if ( indent != null ) 
      stream.write( System.getProperty( "line.separator", "\r\n" ) );
  }
}
